use proc_macro::TokenStream;
use std::any::{Any, TypeId};
use anyhow::{bail, Result};
use quote::ToTokens;
use syn::{AngleBracketedGenericArguments, BareFnArg, FnArg, Item, ItemFn, Pat, PatIdent, PatType, ReturnType, Type, TypePath};
use crate::utils::{check, check_rust_fn_with_let_return, check_with_let_return};


pub fn expand(_attr: TokenStream, token: TokenStream) -> Result<TokenStream> {
    let input: Item = syn::parse(token)?;
    if let Item::Fn(ref f) = input {
        expand_fn(f)
    } else {
        bail!("item {:?} not support", input.type_id())
    }
}

fn expand_fn(fnc: &ItemFn) -> Result<TokenStream> {
    let fn_name = fnc.sig.ident.clone();
    let inputs = fnc.sig.inputs.clone();


    let mut args_token = check_with_let_return("args", quote! {
        env.get_args(info)
    });
    for i in 0..inputs.len() {
        let input = &inputs[i];
        match input {
            FnArg::Receiver(_) => {}
            FnArg::Typed(input) => {
                let bare: TypePath = syn::parse(input.ty.clone().into_token_stream().into()).unwrap();
                let name: Pat = syn::parse(input.pat.clone().into_token_stream().into()).unwrap();
                let ty = bare.path.segments[0].ident.to_string();
                let new_one = match ty.as_str() {
                    "f64" => {
                        check(
                            quote! {
                            env.get_double_from(args[#i])
                        })
                    }
                    "i32" => {
                        check(
                            quote! {
                            env.get_int32_from(args[#i])
                        })
                    }

                    "u32" => {
                        check(
                            quote! {
                            env.get_uint32_from(args[#i])
                        })
                    }
                    "i64" => {
                        check(
                            quote! {
                            env.get_int64_from(args[#i])
                        })
                    }
                    "bool" => {
                        check(
                            quote! {
                            env.get_bool_from(args[#i])
                        })
                    }
                    "String" => {
                        check(
                            quote! {
                            env.get_value_string_utf8(args[#i])
                        })
                    }
                    _ => bail!("{ty} not support")
                };

                let line = quote! {
                    let #name = #new_one;
                };
                args_token = quote! {
                    #args_token
                    #line
                }
            }
        }
    }


    let mut block = &fnc.block;
    let result = &fnc.sig.output;
    let mut result_type = quote! {};
    let return_token =
        match result {
            ReturnType::Default => {
                quote! {core::ptr::null_mut()}
            }
            ReturnType::Type(a, b) => {
                let sg: TypePath = syn::parse(b.into_token_stream().into()).unwrap();
                let segment = sg.path.segments.first().unwrap();
                let ident = segment.ident.to_string();
                result_type = quote! {:#b};
                let mut result_ty = "".to_string();

                let token = if ident.as_str() == "Result" {
                    let ty: AngleBracketedGenericArguments = syn::parse(segment.arguments.clone()
                        .into_token_stream().into()).unwrap();

                    result_ty = ty.args.first().unwrap().into_token_stream().to_string();

                    check_rust_fn_with_let_return("result", quote! {
                    result
                })
                } else {
                    result_ty = b.into_token_stream().to_string();
                    quote! {}
                };
                println!("result type: {}", result_ty);

                let out = match result_ty.as_str() {
                    "f64" => {
                        check(
                            quote! {
                            env.create_f64(result)
                        })
                    }
                    "i32" => {
                        check(
                            quote! {
                            env.create_int32(result)
                        })
                    }

                    "u32" => {
                        check(
                            quote! {
                            env.create_uint32(result)
                        })
                    }
                    "i64" => {
                        check(
                            quote! {
                            env.create_int64(result)
                        })
                    }
                    "bool" => {
                        check(
                            quote! {
                            env.create_bool(result)
                        })
                    }
                    "String" => {
                        check(
                            quote! {
                            env.create_string_utf8(result)
                        })
                    }
                    _ => {
                        bail!("return type [{result_ty}] not support")
                    }
                };
                println!("out: {}", out);

                quote! {
                #token
                #out
            }
            }
        };

    let r = quote! {
        extern "C" fn #fn_name(env: oh_napi::napi_env, info: oh_napi::napi_callback_info)->  oh_napi::napi_value{
            unsafe{
                let env = oh_napi::Env::from(env);
                #args_token
                let result #result_type= #block ;

                #return_token
            }
        }
    };


    Ok(TokenStream::from(r))
}